home *** CD-ROM | disk | FTP | other *** search
/ Nebula 2 / Nebula Two.iso / SourceCode / MiscKit1.7.1 / MiscKit / Source / MiscMergeKit / MiscMergeTemplate.m < prev    next >
Encoding:
Text File  |  1996-02-07  |  10.5 KB  |  319 lines

  1. //
  2. //    MiscMergeTemplate.m -- a data container and parser for merge templates
  3. //        Written by Don Yacktman Copyright (c) 1995 by Don Yacktman.
  4. //                Version 1.0.  All rights reserved.
  5. //        This notice may not be removed from this source code.
  6. //
  7. //    This object is included in the MiscKit by permission from the author
  8. //    and its use is governed by the MiscKit license, found in the file
  9. //    "LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
  10. //    for a list of all applicable permissions and restrictions.
  11. //    
  12.  
  13.  
  14. #import <misckit/misckit.h>
  15. #import <misckit/miscmerge.h>
  16. #import <objc/objc-runtime.h>
  17.  
  18. #import    "_MiscMergeCopyCommand.h"
  19. #import    "_MiscMergeFieldCommand.h"
  20. #import    "_MiscMergeDelayedParseCommand.h"
  21.  
  22. @implementation MiscMergeTemplate
  23. /*" This class conatins the template that is used by a merge engine.
  24. It performs two functions:  (1) parse a string or text file into the
  25. commands required by a merge ending and (2) act as a container for
  26. the commands once they have been parsed, providing them to a merge
  27. engine as needed.
  28.  
  29. Typically, MiscMergeTemplate objects are used in a very simple way:  they
  30. are instantiated, given the ASCII text or string to parse, and then passed
  31. to MiscMergeEngine instances as needed.  That's it!
  32.  
  33. It should be noted that template text which is simply copied from the
  34. template into the merged output (ie. an text outside of a merge command)
  35. is actually turned into a special “copy” command by the parsing algorithm.
  36. This allows the merge engine to deal exclusively with MiscMergeCommand
  37. subclasses to perform a merge.  This implementation detail should not
  38. affect anything that would normally be done with this object, but it is
  39. important to understand this fact if attempting to understand the data
  40. structure created by the parsing routines.
  41. "*/
  42.  
  43. // Note: delimiters may be escaped with a "\".
  44. static char startChar = '«';
  45. static char endChar = '»';
  46.  
  47. + (char)startFieldDelimiter
  48. /*" Returns the character used to start a merge command.
  49. "*/
  50. {
  51.     return startChar;
  52. }
  53.  
  54. + (char)endFieldDelimiter
  55. /*" Returns the character used to end a merge command.
  56. "*/
  57. {
  58.     return endChar;
  59. }
  60.  
  61. + setFieldDelimitersStart:(char)aChar1 end:(char)aChar2
  62. /*" Sets the characters used to start and end a merge command.  Returns
  63. self.  It is highly recommended that %{aChar1} and %{aChar2} be different
  64. characters.
  65. "*/
  66. {    
  67.     startChar = aChar1;
  68.     endChar = aChar2;
  69.     return self;
  70. }
  71.  
  72. + getClassForCommand:(MiscString *)aCommand
  73. /*" Given the command string %{aCommand}, this method determines which
  74. MiscMergeCommand subclass implements the merge command.  It returns the
  75. class object needed to create instances of the MiscMergeCommand subclass.
  76.  
  77. This method works by asking the runtime if it can find Objective-C classes
  78. with specific names.  The name that is looked up is build from the first
  79. word found in %{aCommand}.  The first word is turnd to all lower case, with
  80. the first letter upper case, and then sandwiched between “Merge” and
  81. “Command”.  For example, the merge command “«If xxx = y»” has the word “If”
  82. as the first word.  Thus, the class “MergeIfCommand” will be searched for.
  83. If the desired class cannot be found, then it is assumed that the merge
  84. command is giving the name of a field wich should be inserted into the
  85. output document.
  86.  
  87. To avoid name space conflicts, all internal merge commands actually use
  88. a slightly different name.  Thus, there really is no “MergeIfCommand“ to
  89. be found.  This method, when it doesn't find the “MergeIfCommand” class, will
  90. search for another class, with a private name.  That class will be found.
  91. (If it weren't found, then the default “field” command class would be
  92. returned.)  This allows a programmer to override any built in command.
  93. To override the “if” command, simply create a “MergeIfCommand“ class
  94. and it will be found before the built in class.  If a programmer wishes
  95. to make a particular command, such as “omit”, inoperative, this technique
  96. may be used to override with a MiscMergeCommand subclass that does nothing.'
  97.  
  98. Note that if the command string %{aCommand} contains merge commands inside
  99. itself, then a special “delayed command” class will be returned.  That
  100. class will, during a merge, create an engine, perform a merge on its
  101. text, and then parse itself into the correct type of command.  This allows
  102. merges to contain commands that change depending upon the data records.
  103. "*/
  104. {
  105. // Look for "MergeXxxCommand" class for user overrides.
  106. // Failing that, look for "_MiscMergeXxxCommand" for built-in commands.
  107. // Failing that, we assume we are dealing with a field.
  108.     id theClassName = [MiscString new];
  109.     id foundClass = nil;
  110.     id individualCommand = [aCommand wordNum:0];
  111.  
  112.     if ([aCommand numOfChar:startChar]) {
  113.         // may have a nested merge to deal with, if so
  114.         // we  use the "delayed merge" class.
  115.         int i; BOOL flag = NO;
  116.         for (i=0; i<[aCommand numOfChar:startChar]; i++) {
  117.             // see if all delimiters are escaped or not
  118.             if ([aCommand charAt:
  119.                     ([aCommand spotOf:startChar occurrenceNum:i
  120.                     caseSensitive:NO] - 1)] != '\\')
  121.                 flag = YES;
  122.         }
  123.         // found an unescaped delimiter, so delay the parsing until
  124.         // we are actually doing a merge.
  125.         if (flag) return [_MiscMergeDelayedParseCommand class];
  126.     }
  127.  
  128.     [individualCommand trimWhiteSpaces];
  129.     [individualCommand toLower];
  130.     [individualCommand capitalizeEachWord];
  131.     theClassName = [[MiscString alloc] initFromFormat:"Merge%sCommand",
  132.             [individualCommand stringValue]];
  133.     foundClass = objc_lookUpClass([theClassName stringValue]);
  134.     if (!foundClass) {
  135.         theClassName = [[MiscString alloc]
  136.                 initFromFormat:"_MiscMerge%sCommand",
  137.                 [individualCommand stringValue]];
  138.         foundClass = objc_lookUpClass([theClassName stringValue]);
  139.     }
  140.     [theClassName free];
  141.     [individualCommand free];
  142.     return (foundClass ? foundClass : [_MiscMergeFieldCommand class]);
  143. }
  144.  
  145. - init
  146. /*" Initializes the MiscMergeTemplate instance and returns self.  This is the
  147. designated initializer.
  148. "*/
  149. {
  150.     [super init];
  151.     commands = [[List alloc] init];
  152.     return self;
  153. }
  154.  
  155. - initWithString:(MiscString *)aString
  156. /*" Initializes the MiscMergeTemplate instance, parses the template from
  157. %{aString}, and returns self.  This method is provided for convenience.
  158. "*/
  159. {
  160.     [self init];
  161.     [self parseFromString:aString];
  162.     return self;
  163. }
  164.  
  165. - initFromFile:(MiscFile *)aFile
  166. /*" Initializes the MiscMergeTemplate instance, parses the template from
  167. the file pointed to by %{aFile}, and returns self.  This method is provided
  168. for convenience.
  169. "*/
  170. {
  171.     [self init];
  172.     [self parseFromFile:aFile];
  173.     return self;
  174. }
  175.  
  176. - initFromFileNamed:(MiscString *)aFileName
  177. /*" Initializes the MiscMergeTemplate instance, parses the template from
  178. the file named %{aFileName}, and returns self.  This method is provided
  179. for convenience.
  180. "*/
  181. {
  182.     [self init];
  183.     [self parseFromFileNamed:aFileName];
  184.     return self;
  185. }
  186.  
  187. - parseFromString:(MiscString *)aString
  188. /*" Parses the MiscMergeTemplate from %{aString}.  Returns self.
  189. "*/
  190. {
  191.     id tempString = [aString copy];
  192.     int count;
  193.  
  194.     [commands freeObjects];
  195.     while ([tempString length] > 0) {
  196.         MiscString *theText = nil;
  197.         MiscString *theCommand = nil;
  198.         MiscMergeCommand *textCommand, *mergeCommand;
  199.  
  200.         // cut block of text from the string, remove first delimiter
  201. //        theText = [tempString extractPart:0 useAsDelimiter:startChar];
  202.         // can't use extractPart since we want to allow escaped
  203.         // delimiters in the document.
  204.         count = 0;
  205.         while (!theText) {
  206.             int right = [tempString spotOf:startChar
  207.                     occurrenceNum:count caseSensitive:NO] - 1;
  208.             if (right < -1) {
  209.                 theText = [tempString copy];
  210.             } else if (right < 0) {
  211.                 theText = [MiscString new];
  212.             } else {
  213.                 if ([tempString charAt:right] == '\\') {
  214.                     count += 1;
  215.                 } else {
  216.                     theText = [tempString midFrom:0 to:right];
  217.                 }
  218.             }
  219.         }
  220.         [tempString replace:[theText stringValue] with:""];
  221.         [tempString replaceFrom:0 length:1 with:""];
  222.  
  223.         // cut command text from the string, remove closing delimiter
  224.  
  225.         // **** won't allow nested commands since we don't check to
  226.         // see if there are any start delimiters before we get to the
  227.         // end delimiter.  If there are, then we need to count that
  228.         // many end delimiters to get the nested commands... *****
  229.         // This needs to be fixed for the production version!!!
  230.  
  231. //        theCommand = [tempString extractPart:0 useAsDelimiter:endChar];
  232.         count = 0;
  233.         { // scan for end delimiter, allowing for nesting
  234.             int pos, nesting = 0; int right = -2;
  235.             for (pos = 0; pos < [tempString length]; pos++) {
  236.                 if ([tempString charAt:pos] == endChar) {
  237.                     if ((pos < 1) || ([tempString charAt:(pos - 1)] != '\\')) {
  238.                         if (nesting) nesting--;
  239.                         else if (right < -1) {
  240.                             right = pos - 1;
  241.                         }
  242.                     }
  243.                 } else if ([tempString charAt:pos] == startChar) {
  244.                     if ((pos < 1) || ([tempString charAt:(pos - 1)] != '\\')) {
  245.                         nesting++;
  246.                     }
  247.                 }
  248.             }
  249.             if (right < -1) theCommand = [tempString copy];
  250.             else if (right < 1) theCommand = [tempString new];
  251.             else theCommand = [tempString midFrom:0 to:right];
  252.         }
  253.         [tempString replace:[theCommand stringValue] with:""];
  254.         [tempString replaceFrom:0 length:1 with:""];
  255.  
  256.         // parse into command objects
  257.         textCommand = [[_MiscMergeCopyCommand alloc] initFrom:theText];
  258.         mergeCommand = [[[[self class] getClassForCommand:theCommand]
  259.                 alloc] initFrom:theCommand];
  260.  
  261.         // add to our list of commands
  262.         [commands addObject:textCommand];
  263.  
  264.         // (Note: last time through the loop, this is likely to be a
  265.         // bogus command and really shouldn't be added!  However, it
  266.         // may be OK, if the template actually ends with a command.
  267.         // As such, the only place this affects has a special case
  268.         // in it (see MME+Symbols.m where fields are resolved when
  269.         // leaveDelimiters is on).  We probably won't fix this bug
  270.         // since its effects are 100% benign and it is easier than
  271.         // writing a "real" parser for the templates.  :-)
  272.         [commands addObject:mergeCommand]; // need to turn into cmd obj 1st
  273.  
  274.         [theText free];
  275.         [theCommand free];
  276.     }
  277.     return self;
  278. }
  279.  
  280. - parseFromFile:(MiscFile *)aFile
  281. /*" Parses the MiscMergeTemplate from the file pointed to by %{aFileName}.
  282. Returns self.
  283. "*/
  284. {
  285.     MiscString *fileString = [MiscString new];
  286.     [fileString loadFromFile:[aFile fullPath]];
  287.     [self parseFromString:fileString];
  288.     return self;
  289. }
  290.  
  291. - parseFromFileNamed:(MiscString *)aFileName
  292. /*" Parses the MiscMergeTemplate from the file named %{aFileName}.
  293. Returns self.
  294. "*/
  295. {
  296.     MiscString *fileString = [MiscString new];
  297.     [fileString loadFromFile:[aFileName stringValue]];
  298.     [self parseFromString:fileString];
  299.     [fileString free];
  300.     return self;
  301. }
  302.  
  303. - commands
  304. /*" Returns a List object containing, in order, all the merge commands
  305. to be executed by the merge engine.
  306. "*/
  307. {
  308.     return commands;
  309. }
  310.  
  311. - commandAt:(unsigned)index
  312. /*" Returns the ith merge command to be executed by the merge engine.
  313. "*/
  314. {
  315.     return [commands objectAt:index];
  316. }
  317.  
  318. @end
  319.